package org.hawk.gwt.ppc.parser.impl;

import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.lookupNext;
import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.readModifiers;
import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.readTypeName;
import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.readTypeParameters;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.hawk.gwt.ppc.parser.AnnotationDescriptor;
import org.hawk.gwt.ppc.parser.BlockDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.CommaExpressionDescriptor;
import org.hawk.gwt.ppc.parser.ConstructorDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.EmptyExpressionDescriptor;
import org.hawk.gwt.ppc.parser.ExpressionDescriptor;
import org.hawk.gwt.ppc.parser.FieldDescriptor;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.parser.MethodDescriptor;
import org.hawk.gwt.ppc.parser.PreprocessorParseException;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.parser.VariableDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor.ClassDescriptorType;
import org.hawk.gwt.ppc.parser.impl.VariableDescriptorParser.VariableDescriptorImpl;
import org.hawk.gwt.ppc.utils.PreprocessorException;

/**
 * Utilities to help to parse class members.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class MemberUtils {
	private MemberUtils() {}
	
	/**
	 * Parses single member and stores it to the correct parameter.
	 * @param tokenizer
	 * @param parser
	 * @param methods
	 * @param initilizers
	 * @param constructors
	 * @param declaredClasses
	 * @param fields
	 * @param children
	 * @throws PreprocessorException in case of troubles
	 */
	static void parseClassMember(TokenSource tokenizer,
			TokenBasedJavaParser parser,
			List<MethodDescriptor> methods,
			List<BlockDescriptor> initilizers,
			List<ConstructorDescriptor> constructors,
			List<ClassDescriptor> declaredClasses, 
			List<FieldDescriptor> fields, 
			List<Descriptor> children) throws PreprocessorException {
		
		Token first = tokenizer.current();
		
		List<String> modifiersList = new ArrayList<String>(2);
		List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>(1);
		
		if (AnnotationDescriptorParser.parseAnnotations(parser, tokenizer, annotations)) {
			declaredClasses.add((ClassDescriptor) DescriptorType.CLASS.parse(
					parser, tokenizer, modifiersList, JavaKeywords.ANNOTATION));
			return;
		}
		
		readModifiers(modifiersList, tokenizer);
		
		children.add(parsePureClassMember(first, tokenizer, parser, methods, initilizers,
				constructors, declaredClasses, fields, modifiersList, annotations));
	}

	/**
	 * Parses single {@link Enum} type field and adds it to the list.
	 * @param parser
	 * @param tokenizer
	 * @param fields
	 * @param type
	 * @throws PreprocessorException 
	 */
	static void parseEnumField(TokenBasedJavaParser parser,
			TokenSource tokenizer, List<FieldDescriptor> fields, String type) throws PreprocessorException {
		Token first = tokenizer.current();
		
		List<String> modifiersList = new ArrayList<String>(2);
		modifiersList.add(JavaKeywords.PUBLIC);
		modifiersList.add(JavaKeywords.STATIC);
		modifiersList.add(JavaKeywords.FINAL);
		
		List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>(1);
		
		if (AnnotationDescriptorParser.parseAnnotations(parser, tokenizer, annotations)) {
			throw new PreprocessorParseException("Unexpected annotation", first);
		}
		
		lookupNext(tokenizer, false);
		
		List<ExpressionDescriptor> arguments = Collections.emptyList();
		if (tokenizer.current().equalsTo('(')) {
			arguments = getEnumArgs(parser, tokenizer);
		}
		
		ClassDescriptor anonymousClass = null;
		if (tokenizer.current().equalsTo('{')) {
			anonymousClass = (ClassDescriptor)DescriptorType.CLASS.parse(parser, tokenizer,
					Collections.emptyList(), ClassDescriptorType.CLASS.name(), type);
		}
		
		if (tokenizer.current().equalsTo(',')) {
			lookupNext(tokenizer, false);
		} else if (!tokenizer.current().equalsTo(';') &&
				!tokenizer.current().equalsTo('}')) {
			throw new PreprocessorParseException("Unexpected character in the enum fields sequence", tokenizer.current());
		}
		
		ExpressionDescriptor initializer = new LineExpressionDescriptorParser.NewExpression(first, arguments, type, anonymousClass);

		fields.add(new FieldDescriptorImpl(first, ((AbstractDescriptor)initializer).getLastToken(), 
				first.toString(), type, annotations, modifiersList, initializer));
	}

	private static List<ExpressionDescriptor> getEnumArgs(TokenBasedJavaParser parser,
			TokenSource tokenizer) throws PreprocessorException {
		int deep = 1;
		LinkedList<Token> result = new LinkedList<Token>();
		while (deep > 0) {
			lookupNext(tokenizer, false);
			result.add(tokenizer.current());
			if (tokenizer.current().equalsTo('(')) {
				deep++;
			} else if (tokenizer.current().equalsTo(')')) {
				deep--;
			}
		}
		lookupNext(tokenizer, false);
		result.removeLast();
		TokenSource source = new IteratorTokenSource(result.iterator());
		source.next();
		Descriptor args = DescriptorType.LINE_EXPRESSION.parse(parser, source, true);
		if (args instanceof EmptyExpressionDescriptor) {
			return Collections.emptyList();
		} else if (args instanceof CommaExpressionDescriptor) {
			return ((CommaExpressionDescriptor)args).getExpressions();
		}
		return Collections.singletonList((ExpressionDescriptor)args);
	}

	private static Descriptor parsePureClassMember(Token first, TokenSource tokenizer,
			TokenBasedJavaParser parser, List<MethodDescriptor> methods,
			List<BlockDescriptor> initilizers,
			List<ConstructorDescriptor> constructors,
			List<ClassDescriptor> declaredClasses,
			List<FieldDescriptor> fields, 
			List<String> modifiersList,
			List<AnnotationDescriptor> annotations)
			throws PreprocessorException, PreprocessorParseException {
		
		if (DescriptorType.CLASS.accepts(tokenizer.current())) {
			ClassDescriptor clazz = (ClassDescriptor) DescriptorType.CLASS.parse(parser, tokenizer, modifiersList);
			declaredClasses.add(clazz);
			return clazz;
		}
		
		if (DescriptorType.BLOCK.accepts(tokenizer.current())) {
			Descriptor block = DescriptorType.BLOCK.parse(parser, tokenizer, modifiersList);
			initilizers.add((BlockDescriptor) block);
			return block;
		}
		
		return parseNamedClassMember(first, tokenizer, parser, methods, constructors,
				fields, annotations, modifiersList);
	}

	private static Descriptor parseNamedClassMember(Token first, TokenSource tokenizer,
			TokenBasedJavaParser parser, List<MethodDescriptor> methods,
			List<ConstructorDescriptor> constructors,
			List<FieldDescriptor> fields, 
			List<AnnotationDescriptor> annotations,
			List<String> modifiersList)
			throws PreprocessorException, PreprocessorParseException {
		
		StringBuilder typeParams = new StringBuilder();
		StringBuilder typeName = new StringBuilder();
		readTypeParameters(typeParams, tokenizer);
		readTypeName(typeName, tokenizer, true);
		
		if (tokenizer.current().equalsTo('(')) {
			return parseConstructor(first, constructors, parser, tokenizer, modifiersList, 
					annotations, typeName.toString(), typeParams.toString());
		}
		
		String name = getMemberName(tokenizer);
		Token tok = tokenizer.current();
		
		if (tok.equalsTo(';') || tok.equalsTo('=') || tok.equalsTo(',')) {
			return parseField(first, parser, tokenizer, fields, typeName.toString(), 
					typeParams.toString(), name, modifiersList, annotations);
		}
		
		return parseMethod(first, parser, tokenizer, methods, typeName.toString(), 
				typeParams.toString(), name, modifiersList, annotations);
	}

	private static Descriptor parseField(Token first, TokenBasedJavaParser parser, TokenSource tokenizer,
			List<FieldDescriptor> fields, String type, String typeParams,
			String name, List<String> modifiersList, List<AnnotationDescriptor> annotations) 
					throws PreprocessorException {
		if (typeParams.length() > 0) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		if (tokenizer.current().equalsTo(';')) {
			FieldDescriptorImpl field = new FieldDescriptorImpl(first, tokenizer.current(), 
					name, type, annotations, modifiersList, null);
			fields.add(field);
			lookupNext(tokenizer, false);
			return field;
		}

		if (tokenizer.current().equalsTo('=')) {
			lookupNext(tokenizer, false);
			
			TokenSource initializerSource = readFieldInitializer(tokenizer);
			AbstractExpressionDescriptor expression = (AbstractExpressionDescriptor) DescriptorType.LINE_EXPRESSION.parse(parser, initializerSource, true);
			
			FieldDescriptorImpl field = new FieldDescriptorImpl(first, expression.getLastToken(), 
					name, type, annotations, modifiersList, expression);
			fields.add(field);
			if (!tokenizer.current().equalsTo(',')) {
				lookupNext(tokenizer, false);
				return field;
			}
		}
		
		
		
		if (!tokenizer.current().equalsTo(',')) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		
		lookupNext(tokenizer, false);
		name = tokenizer.current().toString();
		lookupNext(tokenizer, false);
		
		return parseField(first, parser, tokenizer, fields, type, typeParams, name, modifiersList, annotations);
	}

	private static TokenSource readFieldInitializer(TokenSource tokenizer) throws PreprocessorException {
		int deep = 0;
		int typeDeep = 0;
		List<Token> list = new LinkedList<Token>();
		boolean isType = false;
		while ((deep > 0 || typeDeep > 0 || !tokenizer.current().equalsTo(',')) && 
				(deep > 0 || !tokenizer.current().equalsTo(';'))) {
			Token current = tokenizer.current();
			list.add(current);
			if (current.equalsTo('{') ||
				current.equalsTo('(') ||
				current.equalsTo('[')) {
				deep++;
			} else if (isType && current.equalsTo('<')) {
				typeDeep++;
			} else if (current.equalsTo('}') ||
				current.equalsTo(')') ||
				current.equalsTo(']')) {
				deep--;
			} else if (isType && current.equalsTo('>')) {
				typeDeep--;
			} else if (isType && current.equalsTo(">>")) {
				typeDeep -= 2;
			} else if (isType && current.equalsTo(">>>")) {
				typeDeep -= 3;
			}
			if (typeDeep == 0) {
				isType = false;
			}
			if (deep == 0 && current.equalsTo(JavaKeywords.NEW)) {
				lookupNext(tokenizer, false);
				isType = true;
				typeDeep = 0;
				list.add(tokenizer.current());
				lookupNext(tokenizer, false);
				while(tokenizer.current().equalsTo('.')) {
					list.add(tokenizer.current());
					lookupNext(tokenizer, false);
					list.add(tokenizer.current());
					lookupNext(tokenizer, false);
				}
			} else {
				lookupNext(tokenizer, false);
			}
		}
		
		TokenSource result = new IteratorTokenSource(list.iterator());
		result.next();
		return result;
	}

	private static String getMemberName(TokenSource tokenizer)
			throws PreprocessorParseException, PreprocessorException {
		Token tok = tokenizer.current();
		if (!tok.isWord()) {
			throw new PreprocessorParseException("Expected member name", tokenizer.current());
		}
		
		lookupNext(tokenizer, false);
		
		return tok.toString();
	}

	private static Descriptor parseConstructor(Token first, 
			List<ConstructorDescriptor> constructors, 
			 TokenBasedJavaParser parser, TokenSource tokenizer,
			List<String> modifiersList, List<AnnotationDescriptor> annotations, 
			String name, String typeParams) 
					throws PreprocessorException {

		List<VariableDescriptor> parameters = new ArrayList<VariableDescriptor>(2);
		
		boolean isVarArgs = parseParameters(parser, tokenizer, parameters);
		
		List<String> exceptions = readThrowsSection(parser, tokenizer);
		
		BlockDescriptor body = (BlockDescriptor) DescriptorType.BLOCK.parse(parser, tokenizer);
		Token last = ((AbstractDescriptor)body).getLastToken();
		
		ConstructorDescriptorImpl result = new ConstructorDescriptorImpl(first, last, body, modifiersList, 
				annotations, parameters, typeParams, exceptions, new ArrayList<Descriptor>(), isVarArgs);
		constructors.add(result);
		
		return result;
	}
	
	private static boolean parseParameters(TokenBasedJavaParser parser, 
			TokenSource tokenizer, List<VariableDescriptor> parameters) throws PreprocessorException {
		lookupNext(tokenizer, false);
		while (!tokenizer.current().equalsTo(')')) {
			parameters.add((VariableDescriptor) DescriptorType.VARIABLE.parse(parser, tokenizer));
			if (tokenizer.current().equalsTo(',')) {
				lookupNext(tokenizer, false);
			} else if (!tokenizer.current().equalsTo(')')) {
				throw new PreprocessorParseException(tokenizer.current());
			}
		}
		lookupNext(tokenizer, false);
		return !parameters.isEmpty() && ((VariableDescriptorImpl)parameters.get(parameters.size() - 1)).isVarArg();
	}

	private static List<String> readThrowsSection(TokenBasedJavaParser parser,
			TokenSource tokenizer) throws PreprocessorException {
		if (!tokenizer.current().equalsTo(JavaKeywords.THROWS)) {
			return Collections.emptyList();
		}
		List<String> result = new ArrayList<String>(2);
		while (!tokenizer.current().equalsTo('{') &&
				!tokenizer.current().equalsTo(';')) {
			lookupNext(tokenizer, false);
			StringBuilder type = new StringBuilder();
			readTypeName(type, tokenizer, false);
			result.add(type.toString());
		}
		
		return result;
	}

	private static Descriptor parseMethod(Token first, 
			TokenBasedJavaParser parser,
			TokenSource tokenizer, 
			List<MethodDescriptor> methods, 
			String returnType,
			String typeParameters, 
			String name, 
			List<String> modifiersList,
			List<AnnotationDescriptor> annotations) 
				throws PreprocessorException {
		List<VariableDescriptor> parameters = new ArrayList<VariableDescriptor>(2);
		
		boolean isVarArgs = parseParameters(parser, tokenizer, parameters);
		
		if (tokenizer.current().equalsTo(JavaKeywords.DEFAULT)) {
			lookupNext(tokenizer, false);
			
			if (!parameters.isEmpty()) {
				throw new PreprocessorParseException(tokenizer.current());
			}
			
			AbstractExpressionDescriptor defaultExpression = (AbstractExpressionDescriptor) 
					DescriptorType.LINE_EXPRESSION.parse(parser, tokenizer);
			
			MethodDescriptorImpl result = new MethodDescriptorImpl(first, defaultExpression.getLastToken(), 
					name, returnType, typeParameters, modifiersList, null, defaultExpression, 
					annotations, parameters, Collections.<String>emptyList(), Collections.<Descriptor>emptyList(), isVarArgs);
			methods.add(result);
			return result; // annotation parameter
		}
			
		List<String> exceptions = readThrowsSection(parser, tokenizer);
		
		if (tokenizer.current().equalsTo(';')) {
			MethodDescriptorImpl result = new MethodDescriptorImpl(first, tokenizer.current(), 
					name, returnType, typeParameters, modifiersList, null, null, 
					annotations, parameters, exceptions, Collections.<Descriptor>emptyList(), isVarArgs);
			methods.add(result);
			lookupNext(tokenizer, false);
			return result; // abstract or native method
		}
		
		BlockDescriptor body = (BlockDescriptor) DescriptorType.BLOCK.parse(parser, tokenizer);
		
		MethodDescriptorImpl result = new MethodDescriptorImpl(first, 
				((AbstractDescriptor)body).getLastToken(),name, returnType, 
				typeParameters, modifiersList, body, null, 
				annotations, parameters, exceptions, 
				Collections.<Descriptor>emptyList(), isVarArgs); 
		methods.add(result);
		
		return result;
	}
}
